﻿#region COPYRIGHT© 2005-2013 Phillip Clark. All rights reserved.

// For licensing information see License.txt (MIT style licensing).

#endregion

using System.Diagnostics.Contracts;

namespace FlitBit.Parse
{
	/// <summary>
	/// Matches a sequence when none of the inner match specifications match.
	/// </summary>
	public sealed class MultiMatchNone : Match
	{
		readonly Match[] _rules;
		readonly int _skipLen;

		internal MultiMatchNone(int skipLength, params Match[] rules)
			: base(MatchKind.None)
		{
			Contract.Requires(rules != null, "rules must not be null");
			Contract.Requires(rules.Length > 0, "rules must be given");
			Contract.Requires(skipLength > 0, "skipLength must be positive");

			_rules = rules;
			_skipLen = skipLength;
		}

		/// <summary>
		///   Determines if the cursor position within the input is at a matching sequence.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a matching sequence; otherwise <em>false</em>.
		/// </returns>
		public override bool At(char[] input, Cursor cursor)
		{
			if (input == null || cursor >= input.Length)
			{
				return false;
			}
			foreach (var m in _rules)
			{
				if (m.At(input, cursor))
				{
					return false;
				}
			}
			return true;
		}

		/// <summary>
		///   Determines if the cursor position within the input is at a matching sequence.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">the cursor</param>
		/// <returns>
		///   <em>true</em> if positioned at a matching sequence; otherwise <em>false</em>.
		/// </returns>
		public override bool At(string input, Cursor cursor)
		{
			if (input == null || cursor >= input.Length)
			{
				return false;
			}
			foreach (var m in _rules)
			{
				if (m.At(input, cursor))
				{
					return false;
				}
			}
			return true;
		}

		/// <summary>
		///   Determines if the instance is equal to another.
		/// </summary>
		/// <param name="obj">the other object</param>
		/// <returns><em>true</em> if equal; otherwise <em>false</em>.</returns>
		public override bool Equals(object obj)
		{
			return obj is MultiMatchNone
				&& this.Equals((MultiMatchNone) obj);
		}

		/// <summary>
		///   Gets an array of non-variable character matches at the front. The elements
		///   in the returned array are positional such that the item at index 0 represents
		///   an exact character match for the first input character, and so on.
		/// </summary>
		/// <returns></returns>
		public override CharMatch[] GetExactCharacterMatchesOnFront()
		{
			return _rules[0].GetExactCharacterMatchesOnFront();
		}

		/// <summary>
		///   Gets the object's hashcode.
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			var prime = 999067; // a random prime
			var h = base.GetHashCode() * prime;
			h ^= _skipLen * prime;
			h ^= _rules.Length * prime;
			foreach (var c in _rules)
			{
				h ^= c.GetHashCode() * prime;
			}
			return h;
		}

		/// <summary>
		///   Tries to match the sequence at the cursor position, advancing the cursor if successful.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <returns>
		///   <em>true</em> if the sequence matched at the initial cursor; otherwise <em>false</em>.
		/// </returns>
		public override bool TryAt(char[] input, ref Cursor cursor)
		{
			if (At(input, cursor))
			{
				cursor += _skipLen;
				return true;
			}
			return false;
		}

		/// <summary>
		///   Tries to match the sequence at the cursor position, advancing the cursor if successful.
		/// </summary>
		/// <param name="input">the input</param>
		/// <param name="cursor">a reference to the cursor</param>
		/// <returns>
		///   <em>true</em> if the sequence matched at the initial cursor; otherwise <em>false</em>.
		/// </returns>
		public override bool TryAt(string input, ref Cursor cursor)
		{
			if (At(input, cursor))
			{
				cursor += _skipLen;
				return true;
			}
			return false;
		}

		/// <summary>
		/// Determines if the other object is equal.
		/// </summary>
		/// <param name="other"></param>
		/// <returns></returns>
		public bool Equals(MultiMatchNone other)
		{
			if (other != null
				&& Equals((Match) other)
				&& _skipLen == other._skipLen
				&& _rules.Length == other._rules.Length)
			{
				for (var i = 0; i < _rules.Length; i++)
				{
					if (!_rules[i].Equals(other._rules[i]))
					{
						return false;
					}
				}
				return true;
			}
			return false;
		}
	}
}